/* Definitions for the byte vector type.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#include <climits>
#include <cstdio>
#include "khipu.hpp"

KP_DECLS_BEGIN

/* Byte vectors and strings are almost the same in our implementation (Since 
 * strings are encoded in UTF-8). In order to reduce code bloat, we provide a
 * "generic" interface that receives one of these instances that encapsulate
 * the differences between the two. */
struct bvargs
{
  int (*getidx_fct) (interpreter *,
    const bvector *, int, int *, uint32_t);
  bvector* (*test) (interpreter *, object);
  int hsize;
  int rtype;
  int flags;
  bvector *empty;

  bvector* alloc (interpreter *interp, uint32_t nbytes) const
    {
      if (!nbytes)
        {
          interp->alval = this->empty->as_obj ();
          return (this->empty);
        }

      evh_guard eg (interp);

      bvector *ret = (bvector *)alloch (nbytes +
        this->hsize + 1, this->rtype);
      ret->vo_full |= this->flags;
      ret->data = (unsigned char *)ret + this->hsize;
      ret->nbytes = nbytes;
      ret->data[nbytes] = '\0';

      interp->alval = ret->as_obj ();
      gc_register (interp, ret, nbytes + this->hsize + 1);
      return (ret);
    }

  int getidx (interpreter *interp, const bvector *bvp,
      int pos, int *outp, uint32_t off = 0) const
    {
      return (this->getidx_fct (interp, bvp, pos, outp, off));
    }
};

static bvector empty_bvector;
static string empty_string;

// Upcast a byte vector and get the UTF-8 length.
#define KPSLEN(ptr)   ((string *)(ptr))->len

/* Strings need their length set after the operation is done. We rely on 
 * strings being subtypes of byte vectors (See above) for this. 
 * Assumes 'args' is bound. */
#define SETLEN(ptr, val)   \
  if (args.rtype == typecode::STR)   \
    KPSLEN(ptr) = (val), ((string *)(ptr))->hval = 0u;   \
  else   \
    (void)0

bvector* bvector::alloc_raw (uint32_t cap)
{
  bvector *retp = (bvector *)alloch (cap + sizeof (*retp), typecode::BVECTOR);
  retp->data = (unsigned char *)&retp[1];
  retp->nbytes = 0;
  return (retp);
}

string* string::alloc_raw (uint32_t cap)
{
  string *retp = (string *)alloch (cap + sizeof (*retp), typecode::STR);
  retp->data = (unsigned char *)&retp[1];
  retp->nbytes = retp->len = 0;
  retp->hval = 0u;
  return (retp);
}

static bvector*
bytes_subseq (interpreter *interp, const bvector *src,
  object ix1, object ix2, const bvargs& args)
{
  int i1, i2, o1, o2;
  if (kp_unlikely (!as<int> (ix1, i1) || !as<int> (ix2, i2)))
    interp->raise ("type-error", "index is not an integer");

  i1 = args.getidx (interp, src, i1, &o1, 1);
  i2 = args.getidx (interp, src, i2, &o2, 1);

  if (i1 > i2)
    interp->raise ("index-error", "indices out of bounds");

  bvector *ret = args.alloc (interp, i2 - i1);
  memcpy (ret->data, src->data + i1, ret->nbytes);
  SETLEN (ret, o2 - o1);
  return (ret);
}

static bvector*
bytes_rpl (interpreter *interp, const bvector *src,
  object ix1, object ix2, object rpl, const bvargs& args)
{
  int i1, i2, o1, o2;

  if (kp_unlikely (!as<int> (ix1, i1) || !as<int> (ix2, i2)))
    interp->raise ("type-error", "indexes must be integers");

  i1 = args.getidx (interp, src, i1, &o1);
  i2 = args.getidx (interp, src, i2, &o2);

  if (kp_unlikely (i1 > i2))
    interp->raise ("index-error", "invalid indices");

  bvector *rp = args.test (interp, rpl);
  bvector *ret = args.alloc (interp, src->nbytes + rp->nbytes - i2 + i1);

  // ret := src[0:i1] + rpl + src[:i2]
  memcpy (ret->data, src->data, i1);
  memcpy (ret->data + i1, rp->data, rp->nbytes);
  memcpy (ret->data + i1 + rp->nbytes,
    src->data + i2, src->nbytes - i2);

  SETLEN (ret, KPSLEN (src) + KPSLEN (rp) - o2 + o1);
  return (ret);
}

static bvector*
bytes_erase (interpreter *interp, const bvector *src,
  object ix1, object ix2, const bvargs& args)
{
  int i1, i2, o1, o2;

  if (kp_unlikely (!as<int> (ix1, i1) || !as<int> (ix2, i2)))
    interp->raise ("type-error", "index is not an integer");

  i1 = args.getidx (interp, src, i1, &o1);
  i2 = args.getidx (interp, src, i2, &o2);

  if (kp_unlikely (i1 > i2))
    interp->raise ("arg-error", "invalid indices");

  bvector *ret = args.alloc (interp, src->nbytes - i2 + i1);
  // ret := src[0:i1] + src[:i2]
  memcpy (ret->data, src->data, i1);
  memcpy (ret->data + i1, src->data + i2, src->nbytes - i2);
  SETLEN (ret, KPSLEN (src) - o2 + o1);
  return (ret);
}

static bvector*
bytes_insert (interpreter *interp, const bvector *src,
  object ix, object ins, const bvargs& args)
{
  int oidx, idx;

  if (kp_unlikely (!as<int> (ix, idx)))
    interp->raise ("type-error", "index is not an integer");

  idx = args.getidx (interp, src, idx, &oidx);
  bvector *insp = args.test (interp, ins);
  bvector *ret = args.alloc (interp, src->nbytes + insp->nbytes);

  // ret := src[0:idx] + insp + src[idx:]
  memcpy (ret->data, src->data, idx);
  memcpy (ret->data + idx, insp->data, insp->nbytes);
  memcpy (ret->data + idx + insp->nbytes,
    src->data + idx, src->nbytes - idx);
  SETLEN (ret, KPSLEN (src) + KPSLEN (insp));
  return (ret);
}

static bvector*
bytes_add (interpreter *interp, const bvector *v1,
  const bvector *v2, const bvargs& args)
{
  if (args.rtype != typecode::STR)
    ;
  else if (!v1->nbytes)
    return ((bvector *)v2);
  else if (!v2->nbytes)
    return ((bvector *)v1);

  bvector *ret = args.alloc (interp, v1->nbytes + v2->nbytes);
  memcpy (ret->data, v1->data, v1->nbytes);
  memcpy (ret->data + v1->nbytes, v2->data, v2->nbytes);
  SETLEN (ret, KPSLEN (v1) + KPSLEN (v2));
  return (ret);
}

static bvector*
bytes_concat (interpreter *interp, object *argv,
  int argc, const bvargs& args)
{
  if (argc == 1)
    return (as_bvector (*argv));

  uint32_t nbytes = 0, len = 0;
  for (int i = 0; i < argc; ++i)
    {
      const bvector *tmp = args.test (interp, argv[i]);
      nbytes += tmp->nbytes;

      if (tmp->vo_type == typecode::STR)
        len += KPSLEN (tmp);
    }

  if (args.rtype == typecode::STR &&
      nbytes == as_bvector(*argv)->nbytes)
    return ((bvector *)as_bvector (*argv));

  bvector *ret = args.alloc (interp, nbytes);
  unsigned char *dstp = ret->data;

  for (int i = 0; i < argc; ++i)
    {
      const bvector *tmp = as_bvector (argv[i]);
      memcpy (dstp, tmp->data, tmp->nbytes);
      dstp += tmp->nbytes;
    }

  SETLEN (ret, len);
  return (ret);
}

static bvector*
bytes_mul (interpreter *interp, const bvector *src,
  int n, const bvargs& args)
{
  if (n <= 0)
    return (args.empty);
  else if (n == 1 && args.rtype == typecode::STR)
    return ((bvector *)src);

  bvector *ret = args.alloc (interp, src->nbytes * n);
  if (src->nbytes == 1)
    memset (ret->data, *src->data, n);
  else
    {
      unsigned char *ptr = ret->data;

      for (int i = 0; i < n; ++i)
        ptr = (unsigned char *)memcpy (ptr,
          src->data, src->nbytes) + src->nbytes;
    }

  SETLEN (ret, KPSLEN (src) * n);
  return (ret);
}

// Byte vector implementation.

static int getidx_b (interpreter *interp,
  const bvector *bp, int idx, int *, uint32_t off)
{
  if ((idx < 0 && (idx += bp->nbytes) < 0) ||
      (uint32_t)idx >= bp->nbytes + off)
    interp->raise_oob (idx, bp->nbytes);

  return (idx);
}

static bvector* test_b (interpreter *interp, object obj)
{
  bvector *ret = as<bvector> (obj);
  if (!ret)
    interp->raise ("type-error", "value is not a bvector");

  return (ret);
}

static const bvargs BV_ARGS = { getidx_b, test_b,
  sizeof (bvector), typecode::BVECTOR, 0, &empty_bvector };

object alloc_bvector (interpreter *interp, uint32_t nbytes)
{
  (void)BV_ARGS.alloc (interp, nbytes);
  return (interp->alval);
}

object get_b (interpreter *interp, object bvec,
  object ix, object dfl)
{
  int idx;

  if (kp_unlikely (dfl != UNBOUND))
    interp->raise_nargs (2, 2, 3);
  else if (kp_unlikely (!as<int> (ix, idx)))
    interp->raise ("type-error", "index is not an integer");

  const bvector *bp = as_bvector (bvec);
  idx = getidx_b (interp, bp, idx, 0, 0);
  kp_return (fixint (bp->data[idx]));
}

object subseq_b (interpreter *interp,
  object src, object ix1, object ix2)
{
  if (ix2 == UNBOUND)
    ix2 = fixint (as_bvector(src)->nbytes);

  kp_return (bytes_subseq(interp, as_bvector (src),
    ix1, ix2, BV_ARGS)->as_obj ());
}

object rpl_b (interpreter *interp, object src,
  object ix1, object ix2, object rpl)
{
  kp_return (bytes_rpl(interp, as_bvector (src),
    ix1, ix2, rpl, BV_ARGS)->as_obj ());
}

object erase_b (interpreter *interp,
  object src, object ix1, object ix2)
{
  kp_return (bytes_erase(interp, as_bvector (src),
    ix1, ix2, BV_ARGS)->as_obj ());
}

object insert_b (interpreter *interp,
  object src, object idx, object ins)
{
  kp_return (bytes_insert(interp, as_bvector (src),
    idx, ins, BV_ARGS)->as_obj ());
}

object nput_b (interpreter *interp,
  object bvec, object ix, object byte)
{
  bvector *bp = as_bvector (bvec);
  int idx, val;

  if (kp_unlikely (!as<int> (ix, idx)))
    interp->raise ("type-error", "index must be an integer");
  else if (kp_unlikely (!as<int> (byte, val) || (unsigned int)val > 0xff))
    interp->raise ("arg-error", "value must be an integer in range [0, 255]");
  else if (kp_unlikely (bp->flagged_p (FLAGS_CONST)))
    interp->raise_const ();

  idx = getidx_b (interp, bp, idx, 0, 0);
  bp->data[idx] = (unsigned char)val;
  kp_return (byte);
}

object add_bb (interpreter *interp, object v1, object v2)
{
  kp_return (bytes_add(interp, as_bvector (v1),
    as_bvector (v2), BV_ARGS)->as_obj ());
}

object concat_b (interpreter *interp, object *argv, int argc)
{
  kp_return (bytes_concat(interp, argv, argc, BV_ARGS)->as_obj ());
}

object mul_ib (interpreter *interp, object ix, object bv)
{
  kp_return (bytes_mul(interp, as_bvector (bv),
    as_int (ix), BV_ARGS)->as_obj ());
}

int cmp_bb (interpreter *interp, object b1, object b2)
{
  const bvector *v1 = as_bvector (b1), *v2 = as_bvector (b2);
  int len = min (v1->nbytes, v2->nbytes);
  int ret = memcmp (v1->data, v2->data, len);
  return (ret == 0 ? v1->nbytes - v2->nbytes : ret);
}

bool eq_bb (interpreter *interp, object b1, object b2)
{
  const bvector *v1 = as_bvector (b1), *v2 = as_bvector (b2);
  return (v1->nbytes == v2->nbytes &&
    memcmp (v1->data, v2->data, v2->nbytes) == 0);
}

uint32_t hashbuf (const void *bp, uint32_t len)
{
  uint32_t ret = len;

  for (uint32_t ix = 0; ix < len; ++ix)
    {
      ret = (ret << 9) | (ret >> 23);
      ret += ((const unsigned char *)bp)[ix];
    }

  return (ret == 0 ? ~(uint32_t)0 : ret);
}

object copy_b (interpreter *interp, object obj, bool)
{
  const bvector *bp = as_bvector (obj);
  object ret = alloc_bvector (interp, bp->nbytes);

  memcpy (as_bvector(ret)->data, bp->data, bp->nbytes);
  kp_return (ret);
}

object iter_b (interpreter *interp, object obj,
  object token, bool adv)
{
  if (token == UNBOUND)
    kp_return (as_bvector(obj)->nbytes == 0 ? NIL : fixint (0));
  else if (!adv)
    return (get_b (interp, obj, token, UNBOUND));
  else if (!fixint_p (token))
    interp->raise ("type-error", "token must be an int");

  int ix = as_int (token) + 1;
  kp_return ((uint32_t)ix >= as_bvector(obj)->nbytes ? NIL : fixint (ix));
}

uint32_t hash_b (interpreter *, object bv)
{
  const bvector *bp = as_bvector (bv);
  return (hashbuf (bp->data, bp->nbytes));
}

object nreverse_b (interpreter *interp, object obj)
{
  bvector *bp = as_bvector (obj);

  if (kp_unlikely (bp->flagged_p (FLAGS_CONST)))
    interp->raise_const ();
  else if (bp->nbytes == 0)
    kp_return (obj);

  for (uint32_t i = 0, j = bp->nbytes - 1; i < j; ++i, --j)
    swap (bp->data[i], bp->data[j]);

  kp_return (obj);
}

object reverse_b (interpreter *interp, object obj)
{
  const bvector *bp = as_bvector (obj);
  if (bp->nbytes == 0)
    kp_return (obj);

  object ret = alloc_bvector (interp, bp->nbytes);
  for (uint32_t i = 0, j = bp->nbytes - 1; i < bp->nbytes; ++i, --j)
    as_bvector(ret)->data[i] = bp->data[j];

  kp_return (ret);
}

int write_b (interpreter *interp, stream *strm, object obj, io_info& info)
{
  const bvector *bp = as_bvector (obj);
  if (info.flags & io_info::FLG_RAW)
    return (strm->write (interp, bp->data, bp->nbytes));

  int ret = 0;

  ret += strm->write (interp, "#\"", 2);
  for (uint32_t i = 0; i < bp->nbytes; ++i)
    {
      int ch = bp->data[i];
      if (ch >= 32 && ch <= 126)   // isprint (ch)
        ret += strm->putb (interp, ch);
      else
        {
          int p1 = ch / 16;
          int p2 = ch % 16;
          char s[] = { '\\', 'x',
            (char)(p1 < 10 ? '0' + p1 : 'a' + p1 - 10),
            (char)(p2 < 10 ? '0' + p2 : 'a' + p2 - 10) };

          ret += strm->write (interp, s, sizeof (s));
        }
    }

  ret += strm->putb (interp, '"');
  return (ret);
}

int64_t pack_b (interpreter *interp, stream *strm, object obj, pack_info&)
{
  const bvector *bvp = as_bvector (obj);
  int64_t ret = 0;

  if (kp_likely (bvp->nbytes <= 0xff))
    ret += strm->putb (interp, (unsigned char)bvp->nbytes);
  else
    ret += strm->putb (interp, 0) + strm->write (interp, &bvp->nbytes);

  return (ret + strm->write (interp, bvp->data, bvp->nbytes));
}

int64_t pack_s (interpreter *interp, stream *strm, object obj, pack_info&)
{
  const string *sp = as_str (obj);
  int64_t ret = 0;

  if (kp_likely (sp->nbytes <= 0xff))
    ret += strm->putb (interp, (unsigned char)sp->nbytes) +
           strm->putb (interp, (unsigned char)sp->len);
  else
    ret += strm->putb (interp, 0) + strm->write (interp, &sp->nbytes) +
           strm->write (interp, &sp->len);

  return (ret + strm->write (interp, sp->data, sp->nbytes));
}

object unpack_b (interpreter *interp, stream *strm, pack_info& info, bool save)
{
  uint32_t nb = strm->getb (interp);
  if ((int)nb < 0 || (nb == 0 && !strm->sread (interp, &nb)))
    kp_return (info.error ("invalid bvector length"));

  bvector *bvp = as_bvector (alloc_bvector (interp, nb + 1));

  if ((bvp->nbytes = strm->read (interp, bvp->data, nb)) != nb)
    kp_return (info.error ("invalid bvector bytes read"));
  else if (save)
    info.add_mapping (interp, *info.offset, bvp->as_obj ());

  bvp->data[bvp->nbytes] = 0;
  kp_return (bvp->as_obj ());
}

object unpack_s (interpreter *interp, stream *strm, pack_info& info, bool save)
{
  uint32_t len, nb = strm->getb (interp);
  if ((int)nb < 0 || (nb == 0 && (!strm->sread (interp, &nb) ||
                                  !strm->sread (interp, &len))) ||
      (nb != 0 && (int)(len = strm->getb (interp)) < 0))
    kp_return (info.error ("invalid string length read"));

  string *sp = as_str (alloc_str (interp, nb));

  if ((sp->nbytes = strm->read (interp, sp->data, nb)) != nb)
    kp_return (info.error ("invalid string characters read"));
  else if (save)
    info.add_mapping (interp, *info.offset, sp->as_obj ());

  sp->len = len;
  sp->data[sp->nbytes] = '\0';
  kp_return (sp->as_obj ());
}

// String implementation.

static int getidx_s (interpreter *interp,
  const bvector *bp, int idx, int *p, uint32_t off)
{
  const string *sp = (const string *)bp;

  if ((idx < 0 && (idx += sp->len) < 0) || (uint32_t)idx >= sp->len + off)
    interp->raise_oob (idx, sp->len);

  return (stridx (sp, *p = idx));
}

static bvector*
test_s (interpreter *interp, object obj)
{
  string *ret = as<string> (obj);
  if (!ret)
    interp->raise ("type-error", "argument is not a string");

  return (ret);
}

static const bvargs STR_ARGS = { getidx_s, test_s,
  sizeof (string), typecode::STR, FLAGS_CONST, &empty_string };

object alloc_str (interpreter *interp, uint32_t nbytes)
{
  string *ret = (string *)STR_ARGS.alloc (interp, nbytes);
  ret->len = ret->hval = 0;
  return (interp->alval);
}

object subseq_s (interpreter *interp,
  object src, object ix1, object ix2)
{
  if (ix2 == UNBOUND)
    ix2 = fixint (len_s (src));

  kp_return (bytes_subseq(interp, as_bvector (src),
    ix1, ix2, STR_ARGS)->as_obj ());
}

object add_ss (interpreter *interp, object s1, object s2)
{
  kp_return (bytes_add(interp, as_bvector (s1),
    as_bvector (s2), STR_ARGS)->as_obj ());
}

object concat_s (interpreter *interp, object *argv, int argc)
{
  kp_return (bytes_concat(interp, argv, argc, STR_ARGS)->as_obj ());
}

static inline object
xadd_sc_cs (interpreter *interp, object s,
  object c, bool str_first_p)
{
  unsigned char buf[6];
  uint32_t len = u32tou8 (buf, as_char (c));
  const string *src = as_str (s);
  string *ret = (string *)STR_ARGS.alloc (interp, src->nbytes + len);

  if (str_first_p)
    fscpy ((char *)memcpy (ret->data, src->data, src->nbytes) +
      src->nbytes, buf, len);
  else
    memcpy (fscpy (ret->data, buf, len), src->data, src->nbytes);

  ret->len = src->len + 1;
  kp_return (interp->alval);
}

object add_sc (interpreter *interp, object s, object c)
{
  return (xadd_sc_cs (interp, s, c, true));
}

object add_cs (interpreter *interp, object c, object s)
{
  return (xadd_sc_cs (interp, s, c, false));
}

object add_cc (interpreter *interp, object c1, object c2)
{
  unsigned char b1[6], b2[6];
  uint32_t n1 = u32tou8 (b1, as_char (c1)), n2 = u32tou8 (b2, as_char (c2));
  string *ret = (string *)STR_ARGS.alloc (interp, n1 + n2);

  fscpy (fscpy (ret->data, b1, n1), b2, n2);
  ret->len = 2;
  kp_return (interp->alval);
}

object mul_ic (interpreter *interp, object ix, object ch)
{
  unsigned char buf[6];
  bvector bv;

  bv.nbytes = u32tou8 (bv.data = buf, as_char (ch));
  kp_return (bytes_mul (interp, &bv, as_int (ix), STR_ARGS)->as_obj ());
}

object mul_is (interpreter *interp, object ix, object s)
{
  kp_return (bytes_mul(interp, as_bvector (s),
    as_int (ix), STR_ARGS)->as_obj ());
}

object get_s (interpreter *interp, object s,
  object ix, object dfl)
{
  int idx;

  if (kp_unlikely (dfl != UNBOUND))
    interp->raise_nargs (2, 2, 3);
  else if (kp_unlikely (!as<int> (ix, idx)))
    interp->raise ("type-error", "index is not an integer");
  
  const string *sp = as_str (s);
  if (idx < 0)
    idx = sp->len + idx;
  
  if (kp_unlikely (idx < 0 || (uint32_t)idx >= sp->len))
    interp->raise_oob (idx, sp->len);

  const unsigned char *ptr = sp->data + stridx (sp, idx);
  kp_return (charobj (u8tou32 (ptr, UTF8_SKIP[*ptr])));
}

uint32_t hash_s (interpreter *, object s)
{
  string *sp = as_str (s);
  if (sp->hval == 0)
    sp->hval = hashbuf (sp->data, sp->nbytes);

  return (sp->hval);
}

object last_b (interpreter *interp, object obj)
{
  const bvector *bvp = as_bvector (obj);
  if (!bvp->nbytes)
    interp->raise_oob (0, 0);

  kp_return (fixint (bvp->data[bvp->nbytes - 1]));
}

// Stream interface.
struct bvstream_data
{
  object bv;
  uint32_t curpos;
  uint32_t nmax;

  unsigned char* data ()
    {
      return (as_bvector(this->bv)->data);
    }

  uint32_t size () const
    {
      return (as_bvector(this->bv)->nbytes);
    }

  void setlen (uint32_t n)
    {
      if (n > this->size ())
        as_bvector(this->bv)->nbytes = n;
    }

  object resize (interpreter *interp, uint32_t n)
    {
      object tmp = alloc_bvector (interp, n);
      memcpy (as_bvector(tmp)->data, this->data (), this->size ());

      as_bvector(tmp)->nbytes = this->size ();
      this->nmax = n;
      return (this->bv = tmp);
    }

  uint32_t nbytes ()
    {
      return (this->nmax);
    }
};

static int
bv_read (interpreter *, stream& strm, void *dstp, uint32_t bytes)
{
  auto dp = (bvstream_data *)strm.cookie;
  uint32_t rb = (uint32_t)(dp->nbytes () - dp->curpos);

  rb = min (rb, bytes);
  memcpy (dstp, dp->data () + dp->curpos, rb);
  dp->curpos += rb;
  return ((int)rb);
}

static int
bv_write (interpreter *interp, stream& strm, const void *src, uint32_t bytes)
{
  auto dp = (bvstream_data *)strm.cookie;

  if (dp->curpos + bytes >= dp->nbytes ())
    strm.extra = dp->resize (interp, upsize (dp->curpos + bytes + 1));

  memcpy (dp->data () + dp->curpos, src, bytes);
  dp->setlen (dp->curpos += bytes);
  return ((int)bytes);
}

static bool
bv_seek (interpreter *interp, stream& strm, spos& pos, int whence)
{
  auto dp = (bvstream_data *)strm.cookie;
  int64_t roff = pos.offset + (whence == SEEK_SET ? 0 :
    whence == SEEK_CUR ? dp->curpos : dp->nbytes ());

  if (roff < 0)
    return (false);
  else if (roff > dp->size ())
    {
      if (!(strm.io_flags & STRM_WRITE) || roff > UINT32_MAX)
        return (false);
      else if (roff > dp->nbytes ())
        strm.extra = dp->resize (interp, upsize (roff + 1));

      memset (dp->data () + dp->size (), 0, roff - dp->size ());
    }

  dp->setlen (dp->curpos = (uint32_t)roff);
  pos.offset = roff;
  return (true);
}

static bool
bv_close (interpreter *, stream& strm)
{
  xfree (strm.cookie);
  return (true);
}

static const stream::xops bv_ops =
{
  bv_read,
  bv_write,
  bv_seek,
  bv_close
};

stream* bvstream (interpreter *interp, object bv, int mode)
{
  if (!(mode & STRM_RDWR))
    return (nullptr);

  auto dp = (bvstream_data *)xmalloc (sizeof (bvstream_data));
  valref tmp (interp, copy_b (interp, bv, false));
  dp->bv = *tmp;
  dp->curpos = 0;
  dp->nmax = as_bvector(*tmp)->nbytes;
  as_bvector(*tmp)->nbytes = 0;

  stream *ret = stream::make (interp, mode, STRM_BUFSIZ, &bv_ops, dp);
  if (!ret)
    xfree (dp);

  ret->extra = dp->bv;
  return (ret);
}

object bvstream_get (interpreter *interp, stream *strm, bool release)
{
  if (strm->io_flags & STRM_CLOSED)
    interp->raise ("arg-error", "stream has been closed");
  else if (!strm->flush (interp))
    kp_return (UNBOUND);

  auto dp = (bvstream_data *)strm->cookie;
  kp_return (release ? dp->bv : copy_b (interp, dp->bv, false));
}

static int
do_init_bvector (interpreter *)
{
  static const unsigned char empty_data[] = { 0 };
  empty_bvector.vo_full = FLAGS_CONST;
  empty_bvector.vo_type = typecode::BVECTOR;
  empty_string.vo_full = FLAGS_CONST;
  empty_string.vo_type = typecode::STR;
  empty_bvector.data = empty_string.data = (unsigned char *)empty_data;
  return (init_op::result_ok);
}

init_op init_bvector (do_init_bvector, "bvector");

KP_DECLS_END

